package battlecruiser.ship.placement.fleet;

import battlecruiser.board.cell.coordinate.Coordinate;
import battlecruiser.ship.placement.ship.Ship;
import battlecruiser.ship.placement.ship.ShipType;
import battlecruiser.shooter.DamageReport;
import battlecruiser.shooter.ShotResult;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author tymoshya
 * @since 18.06.12
 *        <p/>
 *        Fleet contains a collection of player's ships to check:
 *        a) are all ships placed?;
 *        b) did the player lose all his ships?.
 */
public class Fleet {

    private Set<Ship> ships;
    private Map<ShipType, Integer> shipTypesPlaced;
    private Map<ShipType, Integer> shipPlacingRules;
    private Map<Coordinate, Ship> coordinateShipMap;
    private boolean ready;

    Fleet() {
        ready = false;
        this.coordinateShipMap = new HashMap<Coordinate, Ship>();
    }

    /*
     * Should be used only by FleetFactory
     */
    Fleet(Set<Ship> ships, Map<ShipType, Integer> shipTypesPlaced, Map<ShipType, Integer> shipPlacingRules) {
        this.ships = ships;
        this.shipTypesPlaced = shipTypesPlaced;
        this.shipPlacingRules = shipPlacingRules;
        this.coordinateShipMap = new HashMap<Coordinate, Ship>();
    }

    /**
     * @param ship ship to add
     * @return 0 - if ship was added; 1 - if ship was nor added
     */
    public boolean addShip(Ship ship) {
        boolean result = canAddThisShip(ship);
        if (result) {
            ships.add(ship);
            shipTypesPlaced.put(ship.getType(), shipTypesPlaced.get(ship.getType()) + 1);
            for (Coordinate coordinate : ship.getCoordinates()) {
                coordinateShipMap.put(coordinate, ship);
            }
        }
        return result;
    }

    public boolean removeShip(Ship ship) {
        Assert.isTrue(ships.contains(ship));
        shipTypesPlaced.put(ship.getType(), shipTypesPlaced.get(ship.getType()) - 1);
        for (Coordinate coordinate : ship.getCoordinates()) {
            coordinateShipMap.remove(coordinate);
        }
        return ships.remove(ship);
    }

    public void destroyShip(Ship ship) {
        ships.remove(ship);
    }

    public boolean isDeployed() {
        return shipTypesPlaced.equals(shipPlacingRules);
    }

    public boolean isEmpty() {
        return ships.isEmpty();
    }

    public void markReady() {
        Assert.isTrue(isDeployed(), "Please place all ships before starting the game!");
        buildCoordinateShipMap();
        ready = true;
    }

    public Set<Ship> getShips() {
        return ships;
    }

    public boolean isReady() {
        return ready;
    }

    private boolean canAddThisShip(Ship ship) {
        return shipTypesPlaced.get(ship.getType()) < shipPlacingRules.get(ship.getType());
    }

    public Map<ShipType, Integer> getShipPlacingRules() {
        return shipPlacingRules;
    }

    public DamageReport reportDamage(Coordinate target) {
        ShotResult shotResult;
        Ship ship = coordinateShipMap.get(target);
        if (ship != null) {
            shotResult = ShotResult.HIT;
        } else {
            shotResult = ShotResult.MISSED;
        }
        return new DamageReport(target, shotResult, ship);
    }

    public void setShips(Set<Ship> ships) {
        this.ships = ships;
    }

    public void setShipTypesPlaced(Map<ShipType, Integer> shipTypesPlaced) {
        this.shipTypesPlaced = shipTypesPlaced;
    }

    public void setShipPlacingRules(Map<ShipType, Integer> shipPlacingRules) {
        this.shipPlacingRules = shipPlacingRules;
    }

    void buildCoordinateShipMap() {

        for (Ship ship : ships) {
            for (Coordinate coordinate : ship.getCoordinates()) {
                coordinateShipMap.put(coordinate, ship);
            }
        }
    }
}
